//! # 安全资源
//!
//! 表示需要保护的资源，用于权限检查和访问控制。

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// 安全资源
/// 
/// 表示需要保护的资源，包含服务、路径、方法等信息
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Resource {
    /// 服务名称
    pub service: String,
    /// 资源路径
    pub path: String,
    /// HTTP方法
    pub method: String,
    /// 资源分类
    pub category: Option<String>,
    /// 自定义属性
    pub attributes: HashMap<String, String>,
}

impl Resource {
    /// 创建新的资源
    pub fn new(service: String, path: String, method: String) -> Self {
        Self {
            service,
            path,
            method: method.to_uppercase(),
            category: None,
            attributes: HashMap::new(),
        }
    }

    /// 设置资源分类
    pub fn with_category(mut self, category: String) -> Self {
        self.category = Some(category);
        self
    }

    /// 添加属性
    pub fn with_attribute(mut self, key: String, value: String) -> Self {
        self.attributes.insert(key, value);
        self
    }

    /// 添加多个属性
    pub fn with_attributes(mut self, attributes: HashMap<String, String>) -> Self {
        for (key, value) in attributes {
            self.attributes.insert(key, value);
        }
        self
    }

    /// 获取资源唯一标识符
    pub fn get_resource_id(&self) -> String {
        format!("{}:{}:{}", self.service, self.method, self.path)
    }

    /// 获取简化的资源标识符（不包含方法）
    pub fn get_simple_id(&self) -> String {
        format!("{}:{}", self.service, self.path)
    }

    /// 检查是否匹配特定服务
    pub fn matches_service(&self, service: &str) -> bool {
        self.service == service || service == "*"
    }

    /// 检查是否匹配特定路径模式
    pub fn matches_path(&self, pattern: &str) -> bool {
        if pattern == "*" {
            return true;
        }
        
        if pattern.contains('*') {
            // 支持简单的通配符匹配
            let regex_pattern = pattern.replace('*', ".*");
            regex::Regex::new(&format!("^{}$", regex_pattern))
                .map(|re| re.is_match(&self.path))
                .unwrap_or(false)
        } else {
            self.path == pattern
        }
    }

    /// 检查是否匹配特定HTTP方法
    pub fn matches_method(&self, method: &str) -> bool {
        self.method == method.to_uppercase() || method == "*"
    }

    /// 检查资源是否匹配给定的条件
    pub fn matches(&self, service: &str, path: &str, method: &str) -> bool {
        self.matches_service(service) &&
        self.matches_path(path) &&
        self.matches_method(method)
    }

    /// 检查是否为敏感资源
    pub fn is_sensitive(&self) -> bool {
        self.attributes.get("sensitive")
            .map(|v| v == "true")
            .unwrap_or(false) ||
        self.path.contains("/admin") ||
        self.path.contains("/config") ||
        self.path.contains("/security")
    }

    /// 检查是否为公共资源
    pub fn is_public(&self) -> bool {
        self.attributes.get("public")
            .map(|v| v == "true")
            .unwrap_or(false) ||
        self.path.starts_with("/public") ||
        self.path.starts_with("/health") ||
        self.path == "/favicon.ico"
    }

    /// 获取所需的权限
    pub fn get_required_permission(&self) -> String {
        // 默认权限格式：service:method:path
        format!("{}:{}:{}", self.service, self.method.to_lowercase(), self.path)
    }

    /// 获取所需的权限（简化版本）
    pub fn get_simple_permission(&self) -> String {
        // 简化权限格式：service:action
        let action = match self.method.as_str() {
            "GET" | "HEAD" | "OPTIONS" => "read",
            "POST" => "create",
            "PUT" | "PATCH" => "update",
            "DELETE" => "delete",
            _ => "access",
        };
        format!("{}:{}", self.service, action)
    }

    /// 获取属性值
    pub fn get_attribute(&self, key: &str) -> Option<&str> {
        self.attributes.get(key).map(|s| s.as_str())
    }

    /// 检查是否有特定属性
    pub fn has_attribute(&self, key: &str, value: &str) -> bool {
        self.attributes.get(key)
            .map(|v| v == value)
            .unwrap_or(false)
    }

    /// 从URL路径创建资源
    pub fn from_url(service: String, url: &str, method: String) -> Self {
        // 解析URL，提取路径部分
        let path = if url.starts_with("http://") || url.starts_with("https://") {
            url::Url::parse(url)
                .map(|u| u.path().to_string())
                .unwrap_or_else(|_| url.to_string())
        } else {
            url.to_string()
        };

        Self::new(service, path, method)
    }

    /// 创建管理资源
    pub fn admin_resource(service: String, path: String, method: String) -> Self {
        Self::new(service, path, method)
            .with_category("admin".to_string())
            .with_attribute("sensitive".to_string(), "true".to_string())
    }

    /// 创建公共资源
    pub fn public_resource(service: String, path: String, method: String) -> Self {
        Self::new(service, path, method)
            .with_category("public".to_string())
            .with_attribute("public".to_string(), "true".to_string())
    }

    /// 获取资源层次结构
    pub fn get_hierarchy(&self) -> Vec<String> {
        let mut hierarchy = Vec::new();
        let parts: Vec<&str> = self.path.split('/').filter(|p| !p.is_empty()).collect();
        
        for i in 1..=parts.len() {
            let path = format!("/{}", parts[0..i].join("/"));
            hierarchy.push(format!("{}:{}", self.service, path));
        }
        
        hierarchy
    }
}

impl std::fmt::Display for Resource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {} on {}", self.method, self.path, self.service)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_resource_creation() {
        let resource = Resource::new(
            "user-service".to_string(),
            "/api/users".to_string(),
            "GET".to_string(),
        );
        
        assert_eq!(resource.service, "user-service");
        assert_eq!(resource.path, "/api/users");
        assert_eq!(resource.method, "GET");
        assert_eq!(resource.get_resource_id(), "user-service:GET:/api/users");
    }

    #[test]
    fn test_resource_matching() {
        let resource = Resource::new(
            "user-service".to_string(),
            "/api/users/123".to_string(),
            "GET".to_string(),
        );
        
        assert!(resource.matches_service("user-service"));
        assert!(resource.matches_service("*"));
        assert!(!resource.matches_service("order-service"));
        
        assert!(resource.matches_path("/api/users/123"));
        assert!(resource.matches_path("/api/users/*"));
        assert!(resource.matches_path("*"));
        assert!(!resource.matches_path("/api/orders/*"));
        
        assert!(resource.matches_method("GET"));
        assert!(resource.matches_method("get"));
        assert!(resource.matches_method("*"));
        assert!(!resource.matches_method("POST"));
        
        assert!(resource.matches("user-service", "/api/users/*", "GET"));
        assert!(resource.matches("*", "*", "*"));
        assert!(!resource.matches("order-service", "/api/users/*", "GET"));
    }

    #[test]
    fn test_resource_categories() {
        let admin_resource = Resource::admin_resource(
            "admin-service".to_string(),
            "/admin/users".to_string(),
            "DELETE".to_string(),
        );
        
        assert!(admin_resource.is_sensitive());
        assert_eq!(admin_resource.category, Some("admin".to_string()));
        
        let public_resource = Resource::public_resource(
            "public-service".to_string(),
            "/public/info".to_string(),
            "GET".to_string(),
        );
        
        assert!(public_resource.is_public());
        assert_eq!(public_resource.category, Some("public".to_string()));
    }

    #[test]
    fn test_permissions() {
        let resource = Resource::new(
            "user-service".to_string(),
            "/api/users".to_string(),
            "POST".to_string(),
        );
        
        assert_eq!(resource.get_required_permission(), "user-service:post:/api/users");
        assert_eq!(resource.get_simple_permission(), "user-service:create");
        
        let get_resource = Resource::new(
            "user-service".to_string(),
            "/api/users".to_string(),
            "GET".to_string(),
        );
        
        assert_eq!(get_resource.get_simple_permission(), "user-service:read");
    }

    #[test]
    fn test_from_url() {
        let resource = Resource::from_url(
            "api-service".to_string(),
            "https://example.com/api/v1/users?page=1",
            "GET".to_string(),
        );
        
        assert_eq!(resource.service, "api-service");
        assert_eq!(resource.path, "/api/v1/users");
        assert_eq!(resource.method, "GET");
    }

    #[test]
    fn test_hierarchy() {
        let resource = Resource::new(
            "api-service".to_string(),
            "/api/v1/users/profile".to_string(),
            "GET".to_string(),
        );
        
        let hierarchy = resource.get_hierarchy();
        assert_eq!(hierarchy, vec![
            "api-service:/api".to_string(),
            "api-service:/api/v1".to_string(),
            "api-service:/api/v1/users".to_string(),
            "api-service:/api/v1/users/profile".to_string(),
        ]);
    }

    #[test]
    fn test_attributes() {
        let resource = Resource::new(
            "test-service".to_string(),
            "/test".to_string(),
            "GET".to_string(),
        )
        .with_attribute("rate_limit".to_string(), "100".to_string())
        .with_attribute("cache_ttl".to_string(), "300".to_string());
        
        assert_eq!(resource.get_attribute("rate_limit"), Some("100"));
        assert_eq!(resource.get_attribute("cache_ttl"), Some("300"));
        assert!(resource.has_attribute("rate_limit", "100"));
        assert!(!resource.has_attribute("rate_limit", "200"));
    }

    #[test]
    fn test_display() {
        let resource = Resource::new(
            "user-service".to_string(),
            "/api/users".to_string(),
            "GET".to_string(),
        );
        
        assert_eq!(format!("{}", resource), "GET /api/users on user-service");
    }
}